/**
 * 
 */
package com.lvmama.vst.batis.datasource;

import java.lang.reflect.Method;

import javax.sql.DataSource;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * 
 * DynamicDataSourceAspect
 * 
 * @author baolm
 *
 */
@Aspect
@Order(1)
@Component 

public class DynamicDataSourceTransactionAspect { 

	private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceTransactionAspect.class);

	@Autowired
	private DataSource dataSource;
	
	@Around("execution(* com.lvmama..*.*(..)) && @annotation(trans)")
	public Object aspectTrans(ProceedingJoinPoint jp, Transactional trans) throws Throwable {

		// Luocheng Tang, don't do anything if not master/slave case
		if (!this.dataSource.getClass().getName().contains("DynamicDataSource")) {
			return jp.proceed(); 
		} 
		
		Object target = jp.getTarget();
		MethodSignature methodSignature = (MethodSignature) jp.getSignature();
		Method method = methodSignature.getMethod();

		String switchPoint = "[METHOD]" + target.getClass().getName() + "." + method.getName();

		String current = DataSourceContextHolder.getCurrentDataSource();
		// first one, set current
		if (null == current || current.isEmpty()) {
			boolean readOnly = trans.readOnly();
			DataSourceType dataSourceType = readOnly ? DataSourceType.READ : DataSourceType.WRITE;
			return switchDataSouce(jp, dataSourceType, switchPoint);
		}

		// outside is read, and inside is write and set
		// propagation=Propagation.REQUIRES_NEW, then switch to write.
		if (DataSourceType.READ.name().equals(current)) {
			boolean readOnly = trans.readOnly();
			if (!readOnly) {
				if (trans.propagation().equals(Propagation.REQUIRES_NEW))
					return switchDataSouce(jp, DataSourceType.WRITE, switchPoint);
				else
					throw new RuntimeException(
							"Transactional config error, can't switch to write data source for outside is readOnly, or use @Transactional(propagation = Propagation.REQUIRES_NEW), at "
									+ switchPoint);
			}
		}

		return jp.proceed();
	}

	private Object switchDataSouce(ProceedingJoinPoint jp, DataSourceType dataSourceType, String switchPoint)
			throws Throwable {
		DataSourceContextHolder.setDataSource(dataSourceType.name());
		logger.warn("Begining [switchPoint={}] Routing DataSource of [{}]", switchPoint, dataSourceType);
		Object result;
		try {
			result = jp.proceed();
		} catch (Throwable e) {
			throw e;
		} finally {
			DataSourceContextHolder.clearDataSource();
			logger.warn("After [switchPoint={}] Clear Routing DataSource of [{}]", switchPoint, dataSourceType);
		}
		return result;
	}
}
